#include "Bundles.h"
#include "Handlers.h"
#include "XMLTree.h"
#include "Bundle.h"
#include "HBSD.h"
#include "ConfigFile.h"
#include "Requester.h"
#include "GBOF.h"
#include "Logging.h"

using namespace std;

Bundles::Bundles(Handlers* router) 
{
	this->router = router;
	int hashCapacity = HBSD::routerConf->getInt(string("bundlesActiveCapacity"), DEFAULT_ACTIVE_CAPACITY);

	sem_init(&bundlesLock, 0, 1);
}

Bundles::~Bundles()
{
	sem_destroy(&bundlesLock);

	localidGBOFMap.clear();

	while(!activeBundles.empty())
	{
		map<string, Bundle*>::iterator iter = activeBundles.begin();
		delete iter->second;
		activeBundles.erase(iter);
	}

} 

Bundle * Bundles::newBundleReported(XMLTree* event) 
{
	Bundle* bundle = new Bundle(this);
	long localId = bundle->initFromReport(event);
	if (localId < 0) 
	{
		return NULL;
	}
	return addIfNew(bundle, localId);
}


Bundle* Bundles::newBundle(XMLTree* evtBundleRcvd) 
{
	// DTN will normally detect duplicate bundles and not deliver
	// a notification to us. But, still, we check.
	if (alreadyExists(evtBundleRcvd)) 
	{
		if (HBSD::log->enabled(Logging::DEBUG)) 
		{
			HBSD::log->debug(string("Dropping duplicate bundle"));
		}
		return NULL;
	}
	// Create the instance of the Bundle object for the bundle.
	// Note that a local id of -1 is common: injected bundles
	// get created via a bundle_injected_event, then DTN sends
	// us the bundle_received_event for that same bundle.
	Bundle* bundle = new Bundle(this);
	long localId = bundle->initReceived(evtBundleRcvd);
	if (localId == -1) 
	{
		if (HBSD::log->enabled(Logging::DEBUG)) 
		{
			HBSD::log->debug(string("Bundle likely meta-data from peer"));
		}
		return NULL;
	}

	return addIfNew(bundle, localId);
}

Bundle* Bundles::newInjectedBundle(XMLTree* evtBundleInjected) 
{
	Bundle* bundle = new Bundle(this);
	bundle->initGeneric(evtBundleInjected);
	bundle->injected = true;
	return bundle;
}

Bundle* Bundles::newBundleDelivery(XMLTree* evtBundleDelivery) 
{
	Bundle* bundle = new Bundle(this);
	long localId = bundle->initGeneric(evtBundleDelivery);
	if (localId == -1) 
	{
		return NULL;
	}
	bundle->routingInfo = true;
	return bundle;
}


bool Bundles::alreadyExists(XMLTree* evtBundleRcvd) 
{
	string key;
	try 
	{
		XMLTree* el = evtBundleRcvd->getChildElementRequired(string("gbof_id"));
		key = GBOF::keyFromXML(el);
		if (key.empty()) 
		{
			return true;
		}
	} 
	catch (exception& e) 
	{
		if (HBSD::log->enabled(Logging::ERROR)) 
		{
			HBSD::log->error(string("Ill-formed GBOF in received XML"));
		}

		// Ill-formed: treat as a duplicate.
		return true;
	}

	bool exists = false;
	sem_wait(&bundlesLock);
	map <string,Bundle *>::iterator iter = activeBundles.find(key);
	exists = (iter != activeBundles.end());
	sem_post(&bundlesLock);
	return exists;
}

Bundle* Bundles::getByKey(string gbofKey) 
{
	sem_wait(&bundlesLock);
	Bundle* bundle = activeBundles[gbofKey];
	sem_post(&bundlesLock);
	return bundle;
}

Bundle* Bundles::addIfNew(Bundle* createdBundle, long localId) 
{
	string gbof = GBOF::keyFromBundle(createdBundle);
	sem_wait(&bundlesLock);

	map <long, string>::iterator iter = localidGBOFMap.find(localId);

	if (iter != localidGBOFMap.end()) 
	{
		sem_post(&bundlesLock);
		return NULL;
	}

	localidGBOFMap[localId] = gbof;
	activeBundles[gbof] = createdBundle;
	sem_post(&bundlesLock);
	return createdBundle;
}

Bundle* Bundles::expire(long localId) 
{
	Bundle* bundle = NULL;
	sem_post(&bundlesLock);

	map <long, string>::iterator iter = localidGBOFMap.find(localId);
	string key = iter->second;

	int rem = localidGBOFMap.erase(localId);

	if (!key.empty()) 
	{
		map <string,Bundle *>::iterator iter2 = activeBundles.find(key);
		if(iter2 != activeBundles.end())
		{
			bundle = iter2->second;
			iter2->second = NULL;
			activeBundles.erase(key);
		}

	}

	sem_post(&bundlesLock);
	return bundle;
}


bool Bundles::remove(Bundle* bundle) 
{
	bool knowOf = false;
	sem_wait(&bundlesLock);
	string gbof = localidGBOFMap[bundle->localId];
	if (!gbof.empty()) 
	{
		map <string,Bundle *>::iterator iter = activeBundles.find(gbof);
		delete iter->second;
		activeBundles.erase(iter);
		knowOf = true;
	}
	sem_post(&bundlesLock);
	return knowOf;
}


bool Bundles::deleteBundle(Bundle* bundle) 
{
	bool knowOf = remove(bundle);
	if (knowOf) 
	{
		HBSD::requester->requestDeleteBundle(bundle);
	}
	return knowOf;
}

void Bundles::finished(Bundle* bundle) 
{
	if (!bundle->injected) 
	{
		remove(bundle);
	}
	HBSD::requester->requestDeleteBundle(bundle);
}

bool Bundles::isCurrent(long localId) 
{
	bool current = false;
	sem_wait(&bundlesLock);
	string gbof = localidGBOFMap[localId];
	if (!gbof.empty()) 
	{
		map <string,Bundle *>::iterator iter = activeBundles.find(gbof);

		if (iter != activeBundles.end()) 
		{
			current = true;
		}
	}
	sem_post(&bundlesLock);
	return current;
}

Bundle* Bundles::bundlefromLocalId(long localId) 
{
	Bundle* bundle = NULL;
	sem_wait(&bundlesLock);
	string gbof = localidGBOFMap[localId];
	if (!gbof.empty()) 
	{
		bundle = activeBundles[gbof];
	}
	sem_post(&bundlesLock);
	return bundle;
}


Bundle* Bundles::eventExpired(XMLTree* evtBundleExpired) 
{
	try 
	{
		return expire(xmlLocalId(evtBundleExpired));
	} 
	catch (exception& e) 
	{
		return NULL;
	}
}


Bundle* Bundles::eventDelivered(XMLTree* evtBundleDelivered) 
{
	long localId;
	try 
	{
		localId = xmlLocalId(evtBundleDelivered);
	} 
	catch (exception& e) 
	{
		return NULL;
	}

	Bundle* bundle = NULL;
	sem_post(&bundlesLock);

	// The following lookup should fail for an injected bundle. But
	// we still check again later.
	string gbof = localidGBOFMap[localId];
	if (!gbof.empty()) 
	{
		bundle = activeBundles[gbof];
	}

	sem_post(&bundlesLock);

	if (bundle == NULL) 
	{
		return NULL;
	}

	if (bundle->injected) 
	{
		return NULL;
	}

	return bundle;
}


long Bundles::xmlLocalId(XMLTree* element) 
{
	try 
	{
		string id = element->getAttrRequired(string("local_id"));
		long localId = atol(id.c_str());
		return localId;
	} 
	catch (exception& e) 
	{
		//throw new NoSuchElementException();
	}
}


